package aml.parse;

import java.util.List;
import java.util.ArrayList;

import aml.parse.node.*;

public class Parser {
	private Token[] tokens;
	private int ptr = -1;
	private Token sym;
	
	public Parser(Scanner scanner) {
		List<Token> tokens = new ArrayList<Token>();
		while (scanner.hasNext()) {
			tokens.add(scanner.next());
		}
		tokens.add(Token.EOF);
		this.tokens = tokens.toArray(new Token[0]);
	}
	
	public ParseNode parse() {
		return expr();
	}

	private void getsym() {
		sym = tokens[++ptr];
	}
	
	private boolean accept(Token.Type type) {
		return sym.type().equals(type);
	}
	
	private boolean match(String str) {
		return sym.toString().equals(str);
	}
	
	private boolean expect(String str) {
		getsym();
		if (match(str)) return true;
		System.err.println("unexpected \"" + sym + "\" <=> \"" + str + "\" expected");
		return false;
	}
	
	private void putback() {
		--ptr;
	}

	private ParseNode expr() {
		List<ParseNode> nodes = new ArrayList<ParseNode>();
		for (ParseNode c = null; (c = atom()) != null; nodes.add(c)) ;
		return new Expr(nodes);
	}
	
	private ParseNode atom() {
		getsym();
		if (accept(Token.Type.EOL)) return new OpEOL();
		if (accept(Token.Type.SYMBOL)) return new Sym(sym);
		if (accept(Token.Type.NUMBER)) return new Num(sym);
		if (accept(Token.Type.STRING)) return new Str(sym);
		if (accept(Token.Type.OPERATOR)) {
			if (match("(")) return tuple();
			if (match("[")) return array();
			if (match("{")) return scope();
			return new Op(sym).makeNode();	// Op as an intermediate layer
		}
		return null;
	}
	
	private ParseNode tuple() {
		List<ParseNode> nodes = new ArrayList<ParseNode>();
		for (ParseNode c = null; (c = atom()) != null; nodes.add(c)) ;
		putback();
		if (expect(")")) return new Tuple(nodes);
		return null;
	}
	
	private ParseNode array() {
		List<ParseNode> nodes = new ArrayList<ParseNode>();
		for (ParseNode c = null; (c = atom()) != null; nodes.add(c)) ;
		putback();
		if (expect("]")) return new Array(nodes);
		return null;
	}
	
	private ParseNode scope() {
		ParseNode n = new Scope();
		n.addChild(expr());
		putback();
		if (expect("}")) return n;
		return null;
	}
}